package cn.edu.uestc.topo;

import org.json.JSONArray;
import org.json.JSONObject;

import java.io.*;
import java.util.*;

public class StressGen {

    private static Long initBw = 1000L;
    private static Integer initDelay = 1024;
    private static Integer initHop = 24;

    private List<Node> nodeList = new ArrayList<>();
    private List<Link> linkList = new ArrayList<>();
    private Topology topology = new Topology();

    private List<TunnelInfo> tunnelInfoList = new ArrayList<>();
    private List<Stat> tunnelStatList = new ArrayList<>();

    public StressGen() {

    }

    public void generateTopoResource(String resourceLocation) {
        this.nodeList.clear();
        this.linkList.clear();

        String nodeFileName = resourceLocation + "data/node.dat";
        String linkFileName = resourceLocation + "data/link.dat";

        File nodeFile = new File(nodeFileName);
        List<String> nodeNameList = new ArrayList<>();
        List<String> nodeCityList = new ArrayList<>();

        try {
            String lineContent = null;
            BufferedReader nodeReader = new BufferedReader(new InputStreamReader(new FileInputStream(nodeFile), "UTF-8"));

            nodeReader.readLine();
            while ((lineContent = nodeReader.readLine()) != null) {
                // Format:
                // number node_name node_city
                // 0      BJ1	    Beijing
                String[] strings = lineContent.split("\\s+");
                nodeNameList.add(strings[1]);
                nodeCityList.add(strings[2]);
            }
            nodeReader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        File linkFile = new File(linkFileName);
        List<String> linkNameList = new ArrayList<>();
        List<Long> bwList = new ArrayList<>();
        List<Integer> metricList = new ArrayList<>();
        List<Integer> delayList = new ArrayList<>();

        try {
            String lineContent = null;
            BufferedReader linkReader = new BufferedReader(new InputStreamReader(new FileInputStream(linkFile), "UTF-8"));

            linkReader.readLine();
            while ((lineContent = linkReader.readLine()) != null) {
                // Format:
                // number link_name bandwidth(MB) metric delay
                // 0	  SZ1-SZ2   410000		   100	  20
                String[] strings = lineContent.split("\\s+");
                linkNameList.add(strings[1]);
                bwList.add(Long.parseLong(strings[2]) * 1000);
                metricList.add(Integer.parseInt(strings[3]));
                delayList.add(Integer.parseInt(strings[4]));
            }
            linkReader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Topology region.
        int regionIndex = 1;
        UUID regionID = UUID.randomUUID();
        UUID topoId = UUID.randomUUID();
        topology.setId(topoId);

        System.out.println("\n-------------------------Generate Topology Nodes and Links-------------------------");

        // Node Handle
        int nodeIndex = 0;
        Map<String, UUID> nodeName2IdMap = new HashMap<>();
        while (nodeIndex < nodeNameList.size()) {
            Node node = new Node();

            UUID nodeId = UUID.randomUUID();
            node.setId(nodeId);

            String nodeName = nodeNameList.get(nodeIndex);
            node.setName(nodeName);

            String nodeCity = nodeCityList.get(nodeIndex);
            node.setCity(nodeCity);

            String mgtIP = "192.168." + regionIndex + "." + (nodeIndex + 1);
            node.setMgtIP(mgtIP);

            String loopbackIP = "192.168." + regionIndex + "." + (254 - nodeIndex);
            node.setLoopbackIP(loopbackIP);

            // Node status: active.
            node.setStatus(1);

            // None
            node.setInactiveReason(1);

            String serialNum = "TENCENT_BR_" + nodeName;
            node.setSerialNum(serialNum);

            String objectOId = "UESTCSCIEKB_" + nodeName + "_QPSK";
            node.setObjectOid(objectOId);

            node.setSoftVersion("v1.0.0-beta");

            // Vendor: unknown.
            node.setVendor(65535);

            node.setRegionId(regionID);

            UUID netconfCfgId = UUID.randomUUID();
            node.setNetconfCfgId(netconfCfgId);

            UUID snmpCfgId = UUID.randomUUID();
            node.setSnmpCfgId(snmpCfgId);

            SegmentRoutingCapability segmentRoutingCapability = new SegmentRoutingCapability();
            node.setSrCapability(segmentRoutingCapability);

            List<UUID> ports = new ArrayList<>();
            node.setPortList(ports);

            this.nodeList.add(node);
            nodeName2IdMap.put(nodeName, nodeId);

            nodeIndex++;
        }

        // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        // Link Handle
        int linkIndex = 0;
        while (linkIndex < linkNameList.size()) {

            // Private forward link
            Link priLinkForward = new Link();
            try {
                UUID linkId = UUID.randomUUID();
                priLinkForward.setId(linkId);

                String linkName = linkNameList.get(linkIndex);
                priLinkForward.setName(linkName + "_PRIVATE");

                priLinkForward.setType(1);

                String[] strings = linkName.split("-");

                String srcNodeName = strings[0];
                UUID srcNodeId = nodeName2IdMap.get(srcNodeName);
                priLinkForward.setSrcNodeId(srcNodeId);

                String dstNodeName = strings[1];
                UUID dstNodeId = nodeName2IdMap.get(dstNodeName);
                priLinkForward.setDstNodeId(dstNodeId);

                priLinkForward.setSrcPortId(UUID.randomUUID());
                priLinkForward.setDstPortId(UUID.randomUUID());

                priLinkForward.setStatus(1);
                priLinkForward.setAttributeFlags(0);

                int metric = metricList.get(linkIndex);
                priLinkForward.setMetric(metric);

                long bandwidth = bwList.get(linkIndex);
                priLinkForward.setBandwidth(bandwidth);

                priLinkForward.setReservableBandwidth(bandwidth);
                priLinkForward.setAllocatedBandwidth(0);

                int delay = delayList.get(linkIndex);
                priLinkForward.setDelay(delay);

                priLinkForward.setPolicy(0);

                this.linkList.add(priLinkForward);
            } catch (Exception e) {
                e.printStackTrace();
            }

            // Private backward link
            Link priLinkBackward = new Link();
            try {
                UUID linkId = UUID.randomUUID();
                priLinkBackward.setId(linkId);

                String linkName = linkNameList.get(linkIndex);

                priLinkBackward.setType(1);

                String[] strings = linkName.split("-");

                String srcNodeName = strings[0];
                UUID srcNodeId = nodeName2IdMap.get(srcNodeName);
                String dstNodeName = strings[1];
                UUID dstNodeId = nodeName2IdMap.get(dstNodeName);

                String linkBackName = dstNodeName + "-" + srcNodeName + "_PRIVATE";
                priLinkBackward.setName(linkBackName);

                priLinkBackward.setSrcNodeId(dstNodeId);
                priLinkBackward.setDstNodeId(srcNodeId);

                priLinkBackward.setSrcPortId(UUID.randomUUID());
                priLinkBackward.setDstPortId(UUID.randomUUID());

                priLinkBackward.setStatus(1);
                priLinkBackward.setAttributeFlags(0);

                int metric = metricList.get(linkIndex);
                priLinkBackward.setMetric(metric);

                long bandwidth = bwList.get(linkIndex);
                priLinkBackward.setBandwidth(bandwidth);

                priLinkBackward.setReservableBandwidth(bandwidth);
                priLinkBackward.setAllocatedBandwidth(0);

                int delay = delayList.get(linkIndex);
                priLinkBackward.setDelay(delay);

                priLinkBackward.setPolicy(0);

                this.linkList.add(priLinkBackward);
            } catch (Exception e) {
                e.printStackTrace();
            }

            // Public forward link
            Link pubLinkForward = new Link();
            try {
                UUID linkId = UUID.randomUUID();
                pubLinkForward.setId(linkId);

                String linkName = linkNameList.get(linkIndex);
                pubLinkForward.setName(linkName + "_PUBLIC");

                pubLinkForward.setType(2);

                String[] strings = linkName.split("-");

                String srcNodeName = strings[0];
                UUID srcNodeId = nodeName2IdMap.get(srcNodeName);
                pubLinkForward.setSrcNodeId(srcNodeId);

                String dstNodeName = strings[1];
                UUID dstNodeId = nodeName2IdMap.get(dstNodeName);
                pubLinkForward.setDstNodeId(dstNodeId);

                pubLinkForward.setSrcPortId(UUID.randomUUID());
                pubLinkForward.setDstPortId(UUID.randomUUID());

                pubLinkForward.setStatus(1);
                pubLinkForward.setAttributeFlags(0);

                int metric = metricList.get(linkIndex);
                pubLinkForward.setMetric(metric * 10);

                pubLinkForward.setBandwidth(10000 * 1000);

                pubLinkForward.setReservableBandwidth(10000 * 1000);
                pubLinkForward.setAllocatedBandwidth(0);

                int delay = delayList.get(linkIndex);
                pubLinkForward.setDelay(delay);

                pubLinkForward.setPolicy(0);

                this.linkList.add(pubLinkForward);
            } catch (Exception e) {
                e.printStackTrace();
            }

            // Public backward link
            Link pubLinkBackward = new Link();
            try {
                UUID linkId = UUID.randomUUID();
                pubLinkBackward.setId(linkId);

                String linkName = linkNameList.get(linkIndex);

                pubLinkBackward.setType(2);

                String[] strings = linkName.split("-");

                String srcNodeName = strings[0];
                UUID srcNodeId = nodeName2IdMap.get(srcNodeName);
                String dstNodeName = strings[1];
                UUID dstNodeId = nodeName2IdMap.get(dstNodeName);

                String LinkBackwardName = dstNodeName + "-" + srcNodeName + "_PUBLIC";
                pubLinkBackward.setName(LinkBackwardName);

                pubLinkBackward.setSrcNodeId(dstNodeId);
                pubLinkBackward.setDstNodeId(srcNodeId);

                pubLinkBackward.setSrcPortId(UUID.randomUUID());
                pubLinkBackward.setDstPortId(UUID.randomUUID());

                pubLinkBackward.setStatus(1);
                pubLinkBackward.setAttributeFlags(0);

                int metric = metricList.get(linkIndex);
                pubLinkBackward.setMetric(metric * 10);

                pubLinkBackward.setBandwidth(10000 * 1000); // 10GB

                pubLinkBackward.setReservableBandwidth(10000 * 1000);
                pubLinkBackward.setAllocatedBandwidth(0);

                int delay = delayList.get(linkIndex);
                pubLinkBackward.setDelay(delay);

                pubLinkBackward.setPolicy(0);

                linkList.add(pubLinkBackward);
            } catch (Exception e) {
                e.printStackTrace();
            }

            linkIndex++;
        }

        this.writeTopologyIntoFile(resourceLocation);
    }

    public void generateTunnelInfoStatResource(String resourceLocation, int tunnelCnt) {
        this.tunnelInfoList.clear();
        this.tunnelStatList.clear();

        int nodeCount = this.nodeList.size();
        int topCnt = 0;
        for (int sIndex = 0; sIndex < nodeCount - 1; sIndex++) {
            for (int dIndex = sIndex + 1; dIndex < nodeCount; dIndex++) {
                Node srcNode = this.nodeList.get(sIndex);
                Node dstNode = this.nodeList.get(dIndex);
                if (srcNode.getCity().equals(dstNode.getCity())) {
                    continue;
                }
                topCnt += 8;
            }
        }

        System.out.println("LOOP COUNT = " + topCnt);

        if (tunnelCnt == -1)
            tunnelCnt = topCnt * 4;

        int loops = tunnelCnt / topCnt;
        // Main loop to generate tunnels
        for (int loop = 0; loop < loops; loop++) {
            for (int sIndex = 0; sIndex < nodeList.size() - 1; sIndex++) {
                for (int dIndex = sIndex + 1; dIndex < nodeList.size(); dIndex++) {
                    Node srcNode = this.nodeList.get(sIndex);
                    Node dstNode = this.nodeList.get(dIndex);

                    if (srcNode.getCity().equals(dstNode.getCity())) {
                        continue;
                    }

                    for (int prio = 1; prio <= 4; prio++) {
                        List<UUID> nullList = new ArrayList<>();
                        String passSequence = null;

                        TunnelInfo forwardTunnelInfo = new TunnelInfo();
                        UUID forwardTunnelId = UUID.randomUUID();
                        forwardTunnelInfo.setId(forwardTunnelId);
                        forwardTunnelInfo.setName("TF_" + srcNode.getName() + "-" + dstNode.getName());
                        forwardTunnelInfo.setNodeId(UUID.randomUUID());
                        forwardTunnelInfo.setPortId(UUID.randomUUID());
                        forwardTunnelInfo.setLinkId(UUID.randomUUID());
                        forwardTunnelInfo.setIp("192.168.45.11/24");
                        forwardTunnelInfo.setSrcIP(srcNode.getLoopbackIP());
                        forwardTunnelInfo.setDstIP(dstNode.getLoopbackIP());
                        forwardTunnelInfo.setPriority(prio);
                        forwardTunnelInfo.setBandwidthRequest(initBw);
                        forwardTunnelInfo.setDelayRequest(initDelay);
                        forwardTunnelInfo.setHopRequest(initHop);
                        forwardTunnelInfo.setAvoidLinkList(nullList);
                        forwardTunnelInfo.setAvoidNodeList(nullList);
                        forwardTunnelInfo.setPassLinkList(nullList);
                        forwardTunnelInfo.setPassNodeList(nullList);
                        forwardTunnelInfo.setPassSequence(passSequence);

                        Stat forwardStat = new Stat();
                        UUID forwardStatId = UUID.randomUUID();
                        forwardStat.setId(forwardStatId);
                        forwardStat.setTunnelId(forwardTunnelId);
                        forwardStat.setPriority(prio);
                        forwardStat.setSequenceNumber(0);
                        forwardStat.setMeterFlow(initBw);
                        forwardStat.setMeterDelay(100);
                        forwardStat.setMeterPacketLoss(0);

                        TunnelInfo inverseTunnelInfo = new TunnelInfo();
                        UUID inverseTunnelId = UUID.randomUUID();
                        inverseTunnelInfo.setId(inverseTunnelId);
                        inverseTunnelInfo.setName("TI_" + dstNode.getName() + "-" + srcNode.getName());
                        inverseTunnelInfo.setNodeId(UUID.randomUUID());
                        inverseTunnelInfo.setPortId(UUID.randomUUID());
                        inverseTunnelInfo.setLinkId(UUID.randomUUID());
                        inverseTunnelInfo.setIp("192.168.45.16/24");
                        inverseTunnelInfo.setSrcIP(dstNode.getLoopbackIP());
                        inverseTunnelInfo.setDstIP(srcNode.getLoopbackIP());
                        inverseTunnelInfo.setPriority(prio);
                        inverseTunnelInfo.setBandwidthRequest(initBw);
                        inverseTunnelInfo.setHopRequest(initHop);
                        inverseTunnelInfo.setDelayRequest(initDelay);
                        inverseTunnelInfo.setAvoidLinkList(nullList);
                        inverseTunnelInfo.setAvoidNodeList(nullList);
                        inverseTunnelInfo.setPassLinkList(nullList);
                        inverseTunnelInfo.setPassNodeList(nullList);
                        inverseTunnelInfo.setPassSequence(passSequence);

                        Stat inverseStat = new Stat();
                        UUID id = UUID.randomUUID();
                        inverseStat.setId(id);
                        inverseStat.setTunnelId(inverseTunnelId);
                        inverseStat.setPriority(prio);
                        inverseStat.setSequenceNumber(0);
                        inverseStat.setMeterDelay(100);
                        inverseStat.setMeterFlow(initBw);
                        inverseStat.setMeterPacketLoss(0);

                        tunnelInfoList.add(forwardTunnelInfo);
                        tunnelInfoList.add(inverseTunnelInfo);

                        tunnelStatList.add(forwardStat);
                        tunnelStatList.add(inverseStat);
                    }
                }
            }
        }

        // Random generate remaining tunnels
        int remainTunnelCnt = tunnelCnt - topCnt * loops;
        System.out.println("REMAIN TUNNEL COUNT = " + remainTunnelCnt);
        for (int i = 0; i < remainTunnelCnt; i++) {
            Random random = new Random();
            int srcIndex = random.nextInt(nodeCount);
            int dstIndex = random.nextInt(nodeCount);
            while (srcIndex == dstIndex) {
                dstIndex = random.nextInt(nodeCount);
            }

            int prio = random.nextInt(4) + 1;
            Node srcNode = this.nodeList.get(srcIndex);
            Node dstNode = this.nodeList.get(dstIndex);

            List<UUID> nullList = new ArrayList<>();
            String passSequence = null;

            TunnelInfo tunnelInfo = new TunnelInfo();
            UUID tunnelId = UUID.randomUUID();
            tunnelInfo.setId(tunnelId);
            tunnelInfo.setName("TF_" + srcNode.getName() + "-" + dstNode.getName());
            tunnelInfo.setNodeId(UUID.randomUUID());
            tunnelInfo.setPortId(UUID.randomUUID());
            tunnelInfo.setLinkId(UUID.randomUUID());
            tunnelInfo.setIp("192.168.45.11/24");
            tunnelInfo.setSrcIP(srcNode.getLoopbackIP());
            tunnelInfo.setDstIP(dstNode.getLoopbackIP());
            tunnelInfo.setPriority(prio);
            tunnelInfo.setBandwidthRequest(initBw);
            tunnelInfo.setDelayRequest(initDelay);
            tunnelInfo.setHopRequest(initHop);
            tunnelInfo.setAvoidLinkList(nullList);
            tunnelInfo.setAvoidNodeList(nullList);
            tunnelInfo.setPassLinkList(nullList);
            tunnelInfo.setPassNodeList(nullList);
            tunnelInfo.setPassSequence(passSequence);

            Stat tunnelStat = new Stat();
            UUID tunnelStatId = UUID.randomUUID();
            tunnelStat.setId(tunnelStatId);
            tunnelStat.setTunnelId(tunnelId);
            tunnelStat.setPriority(prio);
            tunnelStat.setSequenceNumber(0);
            tunnelStat.setMeterFlow(initBw);
            tunnelStat.setMeterDelay(100);
            tunnelStat.setMeterPacketLoss(0);

            this.tunnelInfoList.add(tunnelInfo);
            this.tunnelStatList.add(tunnelStat);
        }

        this.writeTunnelInfoStatIntoFile(resourceLocation, tunnelCnt);
    }

    private void writeTopologyIntoFile(String resourceLocation) {

        String topoNodeJsonFile = resourceLocation + "node.json";
        String topoLinkJsonFile = resourceLocation + "link.json";
        String topoTopoJsonFile = resourceLocation + "topo.json";

        // Topology Handle and Json File Written.
        try {
            List<UUID> nodeIdList = new ArrayList<>();
            JSONArray nodeJsonArray = new JSONArray();
            for (Node node : this.nodeList) {
                nodeIdList.add(node.getId());
                nodeJsonArray.put(node.writeToJson());
            }
            JSONObject nodesJO = new JSONObject();
            nodesJO.put("topoNodes", nodeJsonArray);

            List<UUID> linkIdList = new ArrayList<>();
            JSONArray linkJsonArray = new JSONArray();
            for (Link link : linkList) {
                linkIdList.add(link.getId());
                linkJsonArray.put(link.writeToJson());
            }
            JSONObject linksJO = new JSONObject();
            linksJO.put("topoLinks", linkJsonArray);

            topology.setNodeList(nodeIdList);
            topology.setLinkList(linkIdList);

            writeJsonObjectToFile(topoTopoJsonFile, topology.writeToJson());
            writeJsonObjectToFile(topoNodeJsonFile, nodesJO);
            writeJsonObjectToFile(topoLinkJsonFile, linksJO);

        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("------------------------Topology Basic Information------------------------");
        System.out.println("Topo Node Count = " + nodeList.size());
        System.out.println("Topo Link Count = " + linkList.size());
    }

    private void writeTunnelInfoStatIntoFile(String resourceLocation, int tunnelCnt) {

        String parentStr = null;

        if (tunnelCnt < 0)
            parentStr = "standard";
        else {
            int pathCnt = tunnelCnt * 2;
            parentStr = "" + pathCnt;
        }

        String tunnelJsonFile = resourceLocation + parentStr + "/tunnel.json";
        String statJsonFile = resourceLocation + parentStr + "/stat.json";

        try {
            // Tunnel Info
            JSONObject tunnelInfosJson = new JSONObject();
            JSONArray tunnelJsonArray = new JSONArray();
            for (TunnelInfo tunnelInfo : tunnelInfoList) {
                tunnelJsonArray.put(tunnelInfo.writeToJson());
            }
            tunnelInfosJson.put("tunnelInfos", tunnelJsonArray);

            writeJsonObjectToFile(tunnelJsonFile, tunnelInfosJson);

            // Tunnel Stat
            JSONObject statsJson = new JSONObject();
            JSONArray statJsonArray = new JSONArray();
            for (Stat stat : tunnelStatList) {
                statJsonArray.put(stat.writeToJson());
            }
            statsJson.put("networkStats", statJsonArray);

            writeJsonObjectToFile(statJsonFile, statsJson);
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("------------------------TunnelInfoStat Basic Information------------------------");
        System.out.println("TunnelInfo Count = " + this.tunnelInfoList.size());
        System.out.println("TunnelStat Count = " + this.tunnelStatList.size());
    }

    private static int getPriority(int prio, int type) {
        int p = 0;

        if (prio == 1 && type == 2) {
            // copper backup
            p = 1;
        } else if (prio == 1 && type == 1) {
            // copper main
            p = 4;
        } else if (prio == 2 && type == 2) {
            // iron backup
            p = 2;
        } else if (prio == 2 && type == 1) {
            // iron main
            p = 5;
        } else if (prio == 3 && type == 2) {
            // silver backup
            p = 3;
        } else if (prio == 3 && type == 1) {
            // silver main
            p = 6;
        } else if (prio == 4 && type == 2) {
            // gold backup
            p = 7;
        } else if (prio == 4 && type == 1) {
            // gold main
            p = 8;
        }

        return p;
    }

    private static void writeJsonObjectToFile(String fileName, JSONObject jsonObject) {

        System.out.println("Write JSON Object into File " + fileName + " ...");
        File file = new File(fileName);

        if (file.isFile() && file.exists()) {
            try {
                BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
                bufferedWriter.write(jsonObject.toString(4));
                bufferedWriter.flush();
                bufferedWriter.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("Write JSON Object into File Completed.");
    }

}
